/*
 * mm.c
 *
 *  Created on: 2016-11-2
 *      Author: jared
 */

#include "constants.h"
#include "types.h"
#include "mm.h"
#include "defs.h"
#include "x86.h"
#include "proc.h"

extern char data[];
pde_t *kpgdir;          //内核页目录基址

struct run {
	struct run *next;
};

struct {
	uint lock;
	int use_lock;
	struct run *freelist;
} kmem;

void kFreeVMInit(void *vstart, void *vend, int first) {
	easyprint("kFreeVMInit start -- ");
	if (first == 1)
		kmem.use_lock = 0;
	freeVMRange(vstart, vend);
	if (first == 0)
		kmem.use_lock = 1;
	easyprint("kFreeVMInit end\n");
}

void freeVMRange(void *vstart, void *vend) {
	char *p;
	p = (char*) PGROUNDUP((uint)vstart);
	for (; p + PGSIZE <= (char*) vend; p += PGSIZE)
		kFreePage(p);
}

void kFreePage(char *v) {
	struct run *r;

	if (kmem.use_lock)
		acquire(&kmem.lock);
	r = (struct run*) v;
	r->next = kmem.freelist;
	kmem.freelist = r;
	if (kmem.use_lock)
		release(&kmem.lock);
}

static struct kmap {
	void *virt;
	uint phys_start;
	uint phys_end;
	int perm;
} kmap[] = { { (void*) KERNBASE, 0, EXTMEM, PTE_W }, // 1M以下内存，这块物理内存没有使用
		{ (void*) KERNLINK, V2P(KERNLINK), V2P(data), 0 },   // 内核代码段+只读数据
		{ (void*) data, V2P(data), PHYSTOP, PTE_W }, // 内核数据段+可用内存
		{ (void*) DEVSPACE, DEVSPACE, 0, PTE_W }, // more devices
		};

//内核page table初始化，只映射内核空间，即2G+
void kPageTableInit(void) {
	easyprint("kPageTableInit start -- ");
	kpgdir = setupKVM();
	switchKVM();
	easyprint("kPageTableInit end\n");
}

void switchKVM(void) {
	lcr3(v2p(kpgdir));   // 加载页目录基址
}

void switchUVM(struct proc *p) {
	cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
	cpu->gdt[SEG_TSS].s = 0;
	cpu->ts.ss0 = SEG_KDATA << 3;
	cpu->ts.esp0 = (uint) proc->kstack + KSTACKSIZE;
	ltr(SEG_TSS << 3);
	if (p->pgdir == 0)
		panic("switchuvm: no pgdir");
	lcr3(v2p(p->pgdir));  // 切换页目录表
}

pde_t* setupKVM(void) {
	pde_t *pgdir;
	struct kmap *k;

	if ((pgdir = (pde_t*) kAlloc()) == 0)
		return 0;

	memset(pgdir, 0, PGSIZE);
	for (k = kmap; k < &kmap[LENGTH(kmap)]; k++)
		if (mapPages(pgdir, k->virt, k->phys_end - k->phys_start,
				(uint) k->phys_start, k->perm) < 0)
			return 0;
	return pgdir;
}

void initUVM(pde_t *pgdir, char *init, uint sz) {
	char *mem;

	if (sz >= PGSIZE)
		panicInfo("inituvm: more than a page");
	mem = kAlloc();
	memset(mem, 0, PGSIZE);
	mapPages(pgdir, 0, PGSIZE, v2p(mem), PTE_W | PTE_U);
	memmove(mem, init, sz);
}

pte_t * walkPGDir(pde_t *pgdir, const void *va, int alloc) {
	pde_t *pde;
	pte_t *pgtab;

	pde = &pgdir[PDX(va)];
	if (*pde & PTE_P) {
		pgtab = (pte_t*) p2v(PTE_ADDR(*pde)); //pgtab线性地址
	} else {
		if (!alloc || (pgtab = (pte_t*) kAlloc()) == 0) //pgtab线性地址
			return 0;

		memset(pgtab, 0, PGSIZE);
		*pde = v2p(pgtab) | PTE_P | PTE_W | PTE_U;
	}
	return &pgtab[PTX(va)];
}

int mapPages(pde_t *pgdir, void *va, uint size, uint pa, int perm) {
	char *a, *last;
	pte_t *pte;

	a = (char*) PGROUNDDOWN((uint)va);
	last = (char*) PGROUNDDOWN(((uint)va) + size - 1);
	for (;;) {
		if ((pte = walkPGDir(pgdir, a, 1)) == 0)
			return -1;
		if (*pte & PTE_P)
			panic();
		*pte = pa | perm | PTE_P;
		if (a == last)
			break;
		a += PGSIZE;
		pa += PGSIZE;
	}
	return 0;
}

pde_t* copyUVM(pde_t *pgdir, uint sz) {
	pde_t *d;
	pte_t *pte;
	uint pa, i, flags;
	char *mem;

	if ((d = setupKVM()) == 0)
		return 0;
	for (i = 0; i < sz; i += PGSIZE) {
		if ((pte = walkPGDir(pgdir, (void *) i, 0)) == 0)
			panicInfo("copyuvm: pte should exist");
		if (!(*pte & PTE_P))
			panicInfo("copyuvm: page not present");
		pa = PTE_ADDR(*pte);
		flags = PTE_FLAGS(*pte);
		if ((mem = kAlloc()) == 0)
			return 0;
		memmove(mem, (char*) p2v(pa), PGSIZE);
		if (mapPages(d, (void*) i, PGSIZE, v2p(mem), flags) < 0)
			return 0;
	}
	return d;

}

char* kAlloc(void) {
	struct run *r;
	if (kmem.use_lock)
		acquire(&kmem.lock);
	r = kmem.freelist;
	if (r)
		kmem.freelist = r->next;
	if (kmem.use_lock)
		release(&kmem.lock);
	return (char*) r;
}

void seginit(void) {
	struct cpu *c;

	c = &cpus[cpunum()];
	c->gdt[SEG_KCODE] = SEG(STA_X | STA_R, 0, 0xffffffff, 0);
	c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
	c->gdt[SEG_UCODE] = SEG(STA_X | STA_R, 0, 0xffffffff, DPL_USER);
	c->gdt[SEG_UDATA] = SEG(STA_W, 0, 0xffffffff, DPL_USER);

	// kernel cpu段，8个字节，存放当前cpu指针和当前cpu正在运行的进程指针，gs指向这个段，
	//分别用cpu(gs:0)访问当前cpu, proc(gs:4)访问当前cpu正在运行的进程
	c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0);
	lgdt(c->gdt, sizeof(c->gdt));
	loadgs(SEG_KCPU << 3);

	// 初始化cpu(gs:0),proc(gs:4)
	cpu = c;
	proc = 0;
}

